/*
 * @(#)FTPDownlaod.java, 2009-2-19
 *
 * Copyright 2009 SuperV System Integration CO., LTD. All rights reserved.
 */
package org.lex.net.ftp;

import static org.lex.utils.StringUtils.nill;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.log4j.Logger;
import org.lex.utils.IOUtils;

/**
 * 
 * 
 * @author Liu Er Xiao
 * @version 1.0, 2009-2-19
 */
public class FTPDownload {
	public static final int DEFAULT_FTP_PORT = 21;

	public static final int SUCCESS = 0;
	public static final int CONNECT_FAILED = 1;
	public static final int LOGIN_FAILED = 2;
	public static final int FILE_NOT_FOUND = 3;
	public static final int IO_FAILED = 4;

	private final String server;
	private final int port;

	private final String user;
	private final String password;

	private final String path;
	private final String file;

	private final OutputStream out;

	private Logger log = Logger.getLogger(FTPDownload.class);

	public FTPDownload(String server, String user, String password, String path, String file, OutputStream out) {
		this(server, DEFAULT_FTP_PORT, user, password, path, file, out);
	}

	public FTPDownload(String server, int port, String user, String password, String path, String file, OutputStream out) {
		log.info("FTPDownload server=" + server + ", port=" + port + ", user=" + user + ", password=" + password + ", path=" + path + ", file="
				+ file + ", out==null? " + (null == out));
		boolean valid = this.isParameterValid(server, port, user, password, path, file, out);
		log.info("Is parameters valid? " + valid);
		if (false == valid) {
			log.error("Illegal argument, return");
			throw new IllegalArgumentException("Null or empty argument in Constructor");
		}
		this.server = server;
		this.port = port;
		this.user = user;
		this.password = password;
		this.path = path;
		this.file = file;
		this.out = out;
	}

	public int download() {
		FTPClient ftp = new FTPClient();
		try {
			ftp.connect(server, port);
			log.debug("connect [" + server + ":" + port + "]: " + ftp.getReplyString());
		} catch (IOException e) {
			log.error("connect ftp server failed", e);
			this.cleanResources(ftp, null);
			return CONNECT_FAILED;
		}

		try {
			ftp.login(user, password);
			log.debug("login [" + user + ":" + password + "]: " + ftp.getReplyString());
		} catch (IOException e) {
			log.error("login ftp server failed", e);
			this.cleanResources(ftp, null);
			return LOGIN_FAILED;

		}
		int replyCode = ftp.getReplyCode();
		if (false == this.isPositive(replyCode)) {
			log.error("login replyCode=" + replyCode + " is Negative, abort");
			this.cleanResources(ftp, null);
			return LOGIN_FAILED;
		}

		try {
			ftp.changeWorkingDirectory(path);
			log.debug("cwd [" + path + "]: " + ftp.getReplyString());
		} catch (IOException e) {
			log.error("cwd failed", e);
			this.cleanResources(ftp, null);
			return FILE_NOT_FOUND;
		}

		InputStream in = null;
		try {
			in = ftp.retrieveFileStream(file);
			log.debug("retr [" + file + "]: " + ftp.getReplyString());
		} catch (IOException e) {
			log.error("retr failed", e);
			this.cleanResources(ftp, in);
			return FILE_NOT_FOUND;
		}

		if (null == in) {
			log.error("Open ftp input stream failed: RETR returns NULL");
			this.cleanResources(ftp, null);
			return FILE_NOT_FOUND;
		}

		try {
			IOUtils.copyStream(in, out);
		} catch (IOException e) {
			log.error("receiving failed", e);
			this.cleanResources(ftp, in);
			return IO_FAILED;
		}
		this.cleanResources(ftp, in);
		log.info("Download [" + file + "] success");
		return SUCCESS;
	}

	private void cleanResources(FTPClient ftp, InputStream streams) {
		this.closeQuietly(ftp);
		IOUtils.closeQuietly(streams);
	}

	private void closeQuietly(FTPClient ftp) {
		if (null == ftp || false == ftp.isConnected()) {
			return;
		}
		try {
			ftp.logout();
		} catch (Exception e) {
			log.error("FTP logout failed", e);
		}
		try {
			ftp.disconnect();
		} catch (Exception e) {
			log.error("FTP disconnect failed", e);
		}
	}

	private boolean isPositive(int code) {
		return FTPReply.isPositiveCompletion(code) || FTPReply.isPositiveIntermediate(code) || FTPReply.isPositivePreliminary(code);
	}

	private boolean isParameterValid(String server, int port, String user, String password, String path, String file, OutputStream out) {
		boolean valid = true;
		valid = valid && (false == nill(server));
		valid = valid && port >= 0 && port <= 65536;
		valid = valid && null != user;
		valid = valid && null != password;
		valid = valid && null != path;
		valid = valid && (false == nill(file));
		valid = valid && (null != out);
		return valid;
	}
}
